package com.horizon.common.listener;

import com.alibaba.fastjson.JSONObject;
import com.horizon.common.annotation.AuditLogIgnore;
import com.horizon.common.entity.IBaseEntity;
import com.horizon.common.event.AuditLogEvent;
import com.horizon.common.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.event.spi.AbstractEvent;
import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiConsumer;

/**
 * 审计日志事件监听器
 *
 * @author wangfuwei
 * @version 2018-08-09
 */
@Slf4j
@Component
public class AuditLogListener {

    /**
     * 审计日志监听(异步)
     *
     * @param auditLogEvent
     */
    @Async
    @EventListener
    public void onApplicationEvent(AuditLogEvent auditLogEvent) {
        AbstractEvent event = auditLogEvent.getEvent();
        Map<String, Object> logMap = packageLog(event);
        if (logMap == null) return;

        //todo 存储数据库
        log.info(JSONObject.toJSONString(logMap));
    }

    /**
     * 封装Log
     *
     * @param event
     * @return
     */
    private Map<String, Object> packageLog(AbstractEvent event) {
        Map<String, Object> logMap = new HashMap<>();

        String operateType = null;//操作类型
        IBaseEntity entity = null;//当前操作对象
        String[] propertyNames = null;//存储数据库的属性
        List<String> columnNames = new ArrayList<>();//存储数据库属性的字段
        List<Class> propertyTypes = new ArrayList<>();//属性类型
        Object[] values = null;//当前对象属性值
        Object[] oldValues = null;//原对象属性值

        AbstractEntityPersister persister = null;
        if (event instanceof PostInsertEvent) {
            operateType = "insert";
            entity = (IBaseEntity) ((PostInsertEvent) event).getEntity();
            persister = (AbstractEntityPersister) ((PostInsertEvent) event).getPersister();
            propertyNames = persister.getPropertyNames();
            values = ((PostInsertEvent) event).getState();//当前值
        } else if (event instanceof PostUpdateEvent) {
            operateType = "update";
            entity = (IBaseEntity) ((PostUpdateEvent) event).getEntity();
            persister = (AbstractEntityPersister) ((PostUpdateEvent) event).getPersister();
            propertyNames = persister.getPropertyNames();
            values = ((PostUpdateEvent) event).getState();//当前值
            oldValues = ((PostUpdateEvent) event).getOldState();//原始值
        } else if (event instanceof PostDeleteEvent) {
            operateType = "delete";
            entity = (IBaseEntity) ((PostDeleteEvent) event).getEntity();
            persister = (AbstractEntityPersister) ((PostDeleteEvent) event).getPersister();
            propertyNames = persister.getPropertyNames();
            values = ((PostDeleteEvent) event).getDeletedState();//当前值
        }
        if (entity.getClass().isAnnotationPresent(AuditLogIgnore.class)) return null;

        String fdId = entity.getFdId();//ID
        JSONObject logObject = new JSONObject();//Log日志
        JSONObject newObject = new JSONObject();//当前对象
        JSONObject oldObject = new JSONObject();//原对象
        newObject.put("fdId", fdId);
        oldObject.put("fdId", fdId);
        for (Integer index = 0; index < propertyNames.length; index++) {
            String name = propertyNames[index];//属性名称
            String column = persister.getPropertyColumnNames(name)[0];//列名称
            Class type = persister.getPropertyType(name).getReturnedClass();//属性类型
            columnNames.add(column);
            propertyTypes.add(type);

            if (type.isAnnotationPresent(AuditLogIgnore.class)) continue;
            if (type.isAnnotationPresent(Transient.class)) continue;
            if (type.isAnnotationPresent(OneToMany.class)) continue;
            if (type.isAnnotationPresent(ManyToMany.class)) continue;
            if (type.isAnnotationPresent(OneToOne.class) && !type.isAnnotationPresent(JoinColumn.class)) continue;
            if (type.isAnnotationPresent(ManyToOne.class) && !type.isAnnotationPresent(JoinColumn.class)) continue;
            if (Collection.class.isAssignableFrom(type)) continue;

            Object value = values[index];//当前值
            Object oldValue = null;//原始值
            if (oldValues != null) oldValue = oldValues[index];

            BiConsumer<Object, JSONObject> consumer = (Object object, JSONObject jsonObject) -> {
                //当前对象处理
                if (StringUtils.isEmpty(object)) {
                    jsonObject.put(name, "");
                } else if (IBaseEntity.class.isAssignableFrom(type)) {//对象获取ID
                    jsonObject.put(name, ((IBaseEntity) object).getFdId());

                    Object parentId = logMap.get("parentId");
                    if (!StringUtils.isEmpty(parentId)) parentId += ",";
                    parentId += ((IBaseEntity) object).getFdId();
                    logMap.put("parentId", parentId);
                } else if (LocalDate.class.isAssignableFrom(type)) {//日期对象
                    jsonObject.put(name, DateUtil.formatDate((LocalDate) object));
                } else if (LocalDateTime.class.isAssignableFrom(type)) {//日期对象
                    jsonObject.put(name, DateUtil.formatDateTime((LocalDateTime) object));
                } else {
                    jsonObject.put(name, object);
                }
            };
            consumer.accept(value, newObject);
            if (oldValues != null) {
                consumer.accept(oldValue, oldObject);
            }
        }

        if (operateType.equals("update")) {
            logObject.put("new", newObject);
            logObject.put("old", oldObject);
        } else {
            logObject = newObject;
        }

        logMap.put("entity", entity);//对象
        logMap.put("tableName", persister.getTableName());//表名
        logMap.put("operateType", operateType);//操作类型
        logMap.put("fdId", fdId);//fdId
        logMap.put("log", logObject);
        return logMap;
    }
}
